﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Medianamik.Core.Logging;
using System.ComponentModel;
using System.Globalization;
using NLog.Targets;
using NLog;

namespace Medianamik.Core.ImportExport
{
    public  class NodeDataSourceImporter:IDisposable 
    {
        private static ILogger _logger = new ObjectFactory<ILogger>().Get("NodeDataSourceImporterLogger");

        public  NodeDataSourceImporter()
        {
           
        }

        private EventHandlerList _events;
        protected EventHandlerList Events
        {
            get
            {
                return _events ?? (_events = new EventHandlerList());
            }
        }

        #region ImportingNode Event

        private static readonly object _importingNode;
        public event EventHandler<ImportingNodeEventArgs> ImportingNode
        {
            add
            {
                Events.AddHandler(_importingNode, value);
            }
            remove
            {
                Events.RemoveHandler(_importingNode, value);
            }
        }

        protected virtual void OnImportingNode(ImportingNodeEventArgs args)
        {
            var handler = Events[_importingNode] as EventHandler<ImportingNodeEventArgs>;
            if(handler != null)
            {
                handler(this, args);
            }
        }

        #endregion

        #region UpdatingNode Event

        private static readonly object _updatingNode;
        public event EventHandler<ImportingNodeEventArgs> UpdatingNode
        {
            add
            {
                Events.AddHandler(_updatingNode, value);
            }
            remove
            {
                Events.RemoveHandler(_updatingNode, value);
            }
        }

        protected virtual void OnUpdatingNode(ImportingNodeEventArgs args)
        {
            var handler = Events[_updatingNode] as EventHandler<ImportingNodeEventArgs>;
            if (handler != null)
            {
                handler(this, args);
            }
        }

        #endregion

        public virtual void ImportData(NodeDataSourceData pData)
        {
            var nodeIds = GetNodeIds(pData);
            var existingNodes = NodeManager.GetNodes<Node>(nodeIds.ToArray());

            var newNodeIds = nodeIds.Except(existingNodes.Select(n => n.ID));

            var newNodes = (from record in pData.Records
                           join id in newNodeIds on record.NodeId equals id
                           select record).ToList();

            _logger.Info("Starting Importation of " + pData.Records.Count + " node(s)");

            try
            {
                ImportNodes(newNodes);
            }
            catch (Exception e)
            {
                _logger.Error("Une erreur s'est produite lors de l'importation des noeuds", e);
            }

            try
            {
                UpdateNodes(existingNodes.ToList(), pData.Records);
            }
            catch (Exception e)
            {
                _logger.Error("Une erreur s'est produite lors de la mise à jour des noeuds", e);
            }
            
            _logger.Info("End of importation");
            
        }

        private void UpdateNodes(List<Node> existingNodes, List<Record> pRecords)
        {
            //on match les noeuds par pair
            var pairs = (from node in existingNodes
                        join record in pRecords on node.ID equals record.NodeId
                        select new { Node = node, Record = record }).ToList();

            foreach (var pair in pairs)
            {
                foreach (var recordInstance in pair.Record.Instances  )
                {
                    if (!pair.Node.InternalInstances.Contains(CultureInfo.GetCultureInfo(recordInstance.CultureName)))
                    {
                        AppendInstance(pair.Node, recordInstance, pair.Record.Properties ); 
                    }
                }

                var args = new ImportingNodeEventArgs(pair.Node);
                OnUpdatingNode(args);
                if (args.Cancel)
                {
                    _logger.Trace("Node Update Cancelled: " + pair.Node.ID);
                }
                else
                {
                    _logger.Trace("Updating Node: " + pair.Node.ID);
                    NodeManager.SaveNode(pair.Node);
                }
            }

        }

        private void AppendInstance(Node node, RecordInstance recordInstance, List<RecordProperty> recordProps)
        {
            var inst = new NodeInstance(Guid.NewGuid(), node, recordInstance.CultureName, recordInstance.IsActive, recordInstance.CreatedOn);
            node.InternalInstances.Add(inst);
            var pairs = from prop in inst.Properties
                        join recordProp in recordProps on prop.NodeTypePropertyID equals recordProp.PropertyId
                        select new { NodeProperty = prop, RecordProperty = recordProp };

            foreach (var pair in pairs)
            {
                pair.NodeProperty.Value = pair.RecordProperty.Value;
            }
        }

        private List<Guid> GetNodeIds(NodeDataSourceData pData)
        {
            return pData.Records.Select(r => r.NodeId).ToList(); 
        }

        private void ImportNodes(List<Record> pRecords)
        {
            _logger.Info("Start Node Importation");

            //Ordonner les noeuds sous forme d'arbre
            var rootNodes = BuildTreeAndGetRoots(pRecords);
            foreach (var item in rootNodes)
            {
                ImportNodeRecursive(item);
            }

            _logger.Info("Finish Node Importation");
        }
        private void ImportNodeRecursive(RecordTreeItem pTreeItem)
        {
            Node n = new RecordNodeFactory(pTreeItem.Record).GetNode();
            if (n.Instances.Count > 0)
            {
                _logger.Warn("Skipping Node:" + n.ID +". Node already has instances" );
                return;
            }

            pTreeItem.Record.Instances.ForEach(i => AppendInstance(n, i, pTreeItem.Record.Properties));
           
            var args = new ImportingNodeEventArgs(n);
            OnImportingNode(args);
            if (args.Cancel)
            {
                _logger.Trace("Node Importation cancelled:" + n.ID);
            }
            else
            {
                _logger.Trace("Importing Node:" + n.ID);
                NodeManager.SaveNode(n);
            }

            foreach (var item in pTreeItem.Children )
            {
                ImportNodeRecursive(item);
            }
        }

        IEnumerable<RecordTreeItem> BuildTreeAndGetRoots(List<Record> pRecords)
        {
            var lookup = new Dictionary<Guid, RecordTreeItem>();
            pRecords.ForEach(x => lookup.Add(x.NodeId, new RecordTreeItem() { Record = x }));
            foreach (var item in lookup.Values.Where(v=>v.Record.ParentNodeId.HasValue))
            {
                RecordTreeItem proposedParent;
                if (lookup.TryGetValue(item.Record.ParentNodeId.Value , out proposedParent))
                {
                    item.Parent  = proposedParent;
                    proposedParent.Children.Add(item);
                }
            }
            return lookup.Values.Where(x => x.Parent == null);
        }



        #region IDisposable Members

        public void Dispose()
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IDisposable Members

        void IDisposable.Dispose()
        {
            throw new NotImplementedException();
        }

        #endregion
    }

    public class ImportingNodeEventArgs : CancelEventArgs
    {
        public Node Node{get;private set;}
        public ImportingNodeEventArgs(Node pNode)
        {
            Node = pNode;
        }
    }
}
